package collection;

import java.util.Iterator;

import exception.CollectionEmptyException;

public class BinTree<E> extends ABinTree<BinTreeNode<E>, E> {

	protected BinTreeNode<E> root = null;
	protected int size = 0;
	
	public BinTree() {}

	@Override
	public BinTreeNode<E> getParent(BinTreeNode<E> someNode) 
	{
		return someNode.getParent();
	}
	
	@Override
	public BinTreeNode<E> createNode(E element) 
	{
		return new BinTreeNode<E>(element);
	}
	
	public void setParent(BinTreeNode<E> someNode, BinTreeNode<E> parentNode)
	{
		someNode.setParent(parentNode);
	}
	
	public boolean hasLeftChild(BinTreeNode<E> someNode)
	{
		return someNode.getLeftChild() != null;
	}
	
	public boolean hasRightChild(BinTreeNode<E> someNode)
	{
		return someNode.getRightChild() != null;
	}
	
	public BinTreeNode<E> getLeftChild(BinTreeNode<E> someNode)
	{
		return someNode.getLeftChild();
	}
	
	public BinTreeNode<E> getRightChild(BinTreeNode<E> someNode)
	{
		return someNode.getRightChild();
	}
	
	public void addLeftChild(BinTreeNode<E> someNode, BinTreeNode<E> childNode)
	{
		someNode.setLeftChild(childNode);
		this.size += 1;
	}
	
	public void addRightChild(BinTreeNode<E> someNode, BinTreeNode<E> childNode)
	{
		someNode.setRightChild(childNode);
		this.size += 1;
	}
	
	public void addLeftChildElement(BinTreeNode<E> someNode, E element)
	{
		addLeftChild(someNode, createNode(element));
		this.size += 1;
	}
	
	public void addRightChildElement(BinTreeNode<E> someNode, E element)
	{
		addRightChild(someNode, createNode(element));
		this.size += 1;
	}

	@Override
	public Iterable<BinTreeNode<E>> getChildren(BinTreeNode<E> someNode) 
	{
		final SLinkedList<BinTreeNode<E>> childList = new SLinkedList<BinTreeNode<E>>();
		if (someNode.getLeftChild() != null)
			childList.addLast(childList.createNode(someNode.getLeftChild()));
		if (someNode.getRightChild() != null)
			childList.addLast(childList.createNode(someNode.getRightChild()));

		return new Iterable<BinTreeNode<E>>(){

			@Override
			public Iterator<BinTreeNode<E>> iterator() 
			{
				return childList.elementsIterator();
			}
			
		};
	}
	
	@Override
	public void preOrderTraverse(BinTreeNode<E> someNode, ANodeCommand<E> command)
	{
		command.receiveNode(someNode).execute();
		
		if (hasLeftChild(someNode))
		{
			preOrderTraverse(someNode.getLeftChild(), command);
		}
		
		if (hasRightChild(someNode))
		{
			preOrderTraverse(someNode.getRightChild(), command);
		}
	}
	
	@Override
	public void postOrderTraverse(BinTreeNode<E> someNode, ANodeCommand<E> command)
	{
		if (hasLeftChild(someNode))
		{
			postOrderTraverse(someNode.getLeftChild(), command);
		}
		
		if (hasRightChild(someNode))
		{
			postOrderTraverse(someNode.getRightChild(), command);
		}
		
		command.receiveNode(someNode).execute();
	}
	
	public void inOrderTraverse(BinTreeNode<E> someNode, ANodeCommand<E> command)
	{
		if (hasLeftChild(someNode))
		{
			inOrderTraverse(someNode.getLeftChild(), command);
		}
		
		command.receiveNode(someNode).execute();
		
		if (hasRightChild(someNode))
		{
			inOrderTraverse(someNode.getRightChild(), command);
		}
	}
	
	public void attach(BinTreeNode<E> someNode, BinTree<E> leftTree, BinTree<E> rightTree)
	{
		if (isInternal(someNode))
			throw new IllegalArgumentException("Attached node is not an external node!");
		
		if (!leftTree.isEmpty())
		{
			someNode.setLeftChild(leftTree.getRoot());
			leftTree.getRoot().setParent(someNode);
			this.size += leftTree.size();
		}
		
		if (!rightTree.isEmpty())
		{
			someNode.setRightChild(rightTree.getRoot());
			rightTree.getRoot().setParent(someNode);
			this.size += rightTree.size();
		}
	}

	@Override
	public int size() 
	{
		return this.size;
	}

	@Override
	public BinTreeNode<E> getRoot() throws CollectionEmptyException 
	{
		if (isEmpty())
			throw new CollectionEmptyException("Binary Tree");
		else
			return this.root;
	}

	@Override
	public BinTreeNode<E> createRoot(E element) 
	{
		this.root = createNode(element);
		this.size += 1;
		return this.root;
	}

	@Override
	public boolean isRoot(BinTreeNode<E> someNode) 
	{
		return someNode == this.root;
	}

	@Override
	public void removeNode(BinTreeNode<E> someNode) 
	{
		BinTreeNode<E> parentNode = getParent(someNode);
		if (parentNode != null)
		{
			if (parentNode.getLeftChild() == someNode)
			{
				parentNode.setLeftChild(null);
				someNode.clear();
			}
			else if (parentNode.getRightChild() == someNode)
			{
				parentNode.setRightChild(null);
				someNode.clear();
			}
			else
			{
				throw new IllegalStateException("Node to remove is neither its parent's left child nor right child!");
			}
		}
	}

}
